package cn.yiyuan.car.service.impl;

import cn.yiyuan.base.customCode.BaseConstants;
import cn.yiyuan.base.query.BaseQuery;
import cn.yiyuan.base.service.impl.BaseServiceImpl;
import cn.yiyuan.base.until.PageInfo;
import cn.yiyuan.car.entity.CarTypeEntity;
import cn.yiyuan.car.mapper.CarTypeMapper;
import cn.yiyuan.car.service.ICarTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.yaml.snakeyaml.constructor.BaseConstructor;

import java.io.Serializable;
import java.util.*;

/**
 * @user m
 * @project car_mall_system
 * @date 2024-01-04
 * @package cn.yiyuan.car.service.impl
 */
@Service
public class CarTypeServiceImpl extends BaseServiceImpl<CarTypeEntity> implements ICarTypeService {

    @Autowired
    private CarTypeMapper carTypeMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 无限极树的查询方案
     * 方案一:递归: 自己调自己,必须有出口
     *      查询无限极树, 总共要查询n+1次数据库,对于数据库的压力太大了
     * 方案二: 循环,只查询一次数据库
     * 并不是说递归不好,如果每次递归都要查询数据库,对于数据库来说压力太大,就不建议使用递归
     * 如果递归里面不涉及数据库操作,使用递归是可以的
     *
     * 缓存经典问题:
     * 缓存穿透: 缓存中没有数据,数据库中也没有数据,就会导致数据库查询后无法构建缓存,后续请求会全部打到数据库,会给数据库造成压力
     *      解决方案:
     *         请求参数限制
     *         缓存默认值 - 实现这个
     *         布隆过滤器
     * 缓存击穿: 某个热点数据过期后,大量请求来同时访问该数据,缓存中没有,这些请求会全部打到数据库,高并发下会冲垮数据库
     *      解决方案:
     *         加锁,保证同一时刻只有一个请求访问数据库 - 实现这个
     *         不设置过期时间
     * 缓存雪崩:
     *   造成原因:
     *      大量数据同时过期
     *      redis故障宕机
     *      他们会造成大量请求会全部打到数据库,从而给数据库造成压力,严重的话,数据库会崩掉,会产生一系列的连锁反应,可能导致整个服务
     *      全部崩掉
     *   解决方案:
     *     大量数据同时过期 - 不解决,缓存数据的key不会太多,一个项目中撑死了最多就100个key,100个key就对应100个代码,每个代码中我们已经加了sync
     *                      锁,所以说,就算全部key都过期了,那同一时刻也最多就100个请求会打到数据库,数据库还是能承受的
     *         均衡设置过期时间,过期时间+随机数, 这个要看业务场景才可以
     *         互斥锁,而是要加分布式锁
     *         双key策略,设置一个主key,一个备key,主key设置过期时间,备key不设置,如果主key过期了,从备key获取数据并返回
     *         后台处理,不设置过期时间也不在业务线程更新缓存,而是后台搞个定时任务定时更新缓存
     *
     *     redis故障宕机
     *          redis集群,多部署几套redis
     *          服务熔断,如果redis宕机了,直接返回一个错误信息给前端,不访问数据库
     *          请求接口限流,限制接口的并发量
     * @return
     */

    @Override
    public List<CarTypeEntity> tree(Long pid) {
        //从rides获取结果
        Object  redis = redisTemplate.opsForValue().get(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
        //如果redis有数据就返回
        if (Objects.nonNull(redis)){
            System.out.println("进入缓存了，并且有数据");
            return (List<CarTypeEntity>)redis;
        }
        // 1w个请求过来
        // 1(放入缓存) 2(没有必要再查数据库,缓存中有)
        // 1 2 3 4
        synchronized (this){
            redis = redisTemplate.opsForValue().get(BaseConstants.CAR_TYPE_TREE_IN_REDIS);

            // 2 如果redis中有数据就直接返回
            if(Objects.nonNull(redis)){
                System.out.println("缓存中有数据...........................");
                return (List<CarTypeEntity>)redis;
            }
            System.out.println("缓存中没有数据.........................");
            // 3 如果没有,就查询数据库,放入redis后返回给前端
           List<CarTypeEntity> carTypes =  getCarTypesByLoop(pid);
            // 判断数据库数据是否为空,如果为空,就缓存默认值  -  解决缓存穿透问题的
            if(carTypes != null && carTypes.size() > 0){
                redisTemplate.opsForValue().set(BaseConstants.CAR_TYPE_TREE_IN_REDIS,carTypes);
            }else{
                CarTypeEntity carType = new CarTypeEntity();
                redisTemplate.opsForValue().set(BaseConstants.CAR_TYPE_TREE_IN_REDIS, Arrays.asList(carType));
            }
            return carTypes;
        }
    }

    /**
     * 循环实现无限级树查询
     * @param pid
     * @return
     */
    private List<CarTypeEntity> getCarTypesByLoop(Long pid){
        // 1 查询所有的车辆类型
        List<CarTypeEntity> carTypes = carTypeMapper.getAll();

        // 把所有的车辆类型转换为一个map,map的key就是这个类型的ID,map的value就是这个类型对象
        Map<Long,CarTypeEntity> map = new HashMap<>();
        for (CarTypeEntity carType : carTypes) {
            map.put(carType.getId(),carType);
        }

        // 定义要返回的集合
        List<CarTypeEntity> result = new ArrayList<>();

        // 2 循环所有的车辆类型
        for (CarTypeEntity carType : carTypes) {
            if(carType.getPid().equals(pid)){
                // 2.1 如果是一级类型,就放入要返回的集合中
                result.add(carType);
            }else{
                // 2.2 如果不是一级,就找到它的父类型,将自己作为儿子放入父类型
                // 方案一:循环所有的车辆类型,判断哪一个的id=当前类型的pid,父类型也在我当前查出来的所有的类型里面
                // 时间复杂度O(n的平方次)
                /*for (CarTypeEntity parent : carTypes) {
                    // 先找出当前类型的父类型
                    if(carType.getPid().equals(parent.getId())){
                        // 将自己作为父类型的子类型放进去
                        parent.getChildren().add(carType);
                        break;
                    }
                }*/
                // 方案二:直接通过当前类型的PID去map中查找父类型
                // 时间复杂度: O(2n)
                CarTypeEntity parent = map.get(carType.getPid());
                parent.getChildren().add(carType);
            }
        }
        return result;
    }
    /**
     * 递归方案查询无限极树
     * @param pid
     * @return
     */
    private List<CarTypeEntity> getCarTypesByRecursion(Long pid) {
        // 1 先根据pid查询出它下面的子类型
        List<CarTypeEntity> carTypes = carTypeMapper.findCarTypeByPid(pid);
        // 2 判断子类型有没有,如果没有就直接返回
        if(carTypes == null || carTypes.size() == 0){
            return carTypes;
        }
        // 3 如果子类型不为空,循环子类型,获取子类型的子类型
        for (CarTypeEntity carType : carTypes) {
            // 以当前类型的ID作为父ID,查询它的子类型
            List<CarTypeEntity> children = getCarTypesByRecursion(carType.getId());
            carType.setChildren(children);
        }
        return carTypes;
    }
    /**
     * 当对车辆类型做增删改的时候 ,要同步修改缓存数据
     * 怎么做??
     * 方案一: 修改数据库以后,给redis重新赋值, 先查询再重新set  -  查询数据库,再重新set
     *      方案一,有可能有这种情况,有的时候 我们对数据库数据做了增删改以后,中间并没有查询过,又再次修改了,那你又要重新给缓存赋值
     * 方案二: 修改数据库后,直接把缓存删除   -  采纳这种,简单
     *
     * 先操作数据库还是先操作缓存??
     *   先操作数据库再操作缓存
     *     如果你先操作缓存再操作数据库,如果你操作数据库的时候报错了,缓存中数据不能回滚
     *     如果先操作数据库,再操作缓存,一旦缓存操作报错,数据库是可以回滚的
     * 以后如果既要操作数据库又要操作其他中间件,一定是先操作数据库再操作其他中间件
     * @param carType
     */
    @Override
    public void insert(CarTypeEntity carType) {
        // 先操作数据库
        super.insert(carType);
        // 删除缓存
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }

    /**
     * 修改
     * @param carType
     * @return
     */
    @Override
    public void update(CarTypeEntity carType) {
        // 先操作数据库
        super.update(carType);
        // 删除缓存
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }

    /**
     * 删除
     * @param id
     */
    @Override
    public void delete(Serializable id) {
        // 先操作数据库
        super.delete(id);
        // 删除缓存
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }




}
